/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.slots.statistic;

import java.util.Collection;

import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException;
import com.alibaba.csp.sentinel.util.TimeUtil;
import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.BlockException;

/**
 * <p>
 * A processor slot that dedicates to real time statistics.
 * When entering this slot, we need to separately count the following
 * information:
 * <ul>
 * <li>{@link ClusterNode}: total statistics of a cluster node of the resource ID.</li>
 * <li>Origin node: statistics of a cluster node from different callers/origins.</li>
 * <li>{@link DefaultNode}: statistics for specific resource name in the specific context.</li>
 * <li>Finally, the sum statistics of all entrances.</li>
 * </ul>
 * </p>
 *
 * @author jialiang.linjl
 * @author Eric Zhao
 */
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        try {
            // Do some checking.
            fireEntry(context, resourceWrapper, node, count, prioritized, args);

            /**
             * hujianfeng
             * 1、这里的node，依然是该context的curEntry中的curNode，也就是：该context正在调用的资源的Slot链上的NodeSelectorSlot中的map里与该context对应的Node
             * 2、这里的increaseThreadNum，不仅仅只是更新当前node的统计，还会在内部更新全局（当前资源维度）clusterNode的统计。
             *
             * DefaultNode 和 ClusterNode 的区别：
             * DefaultNode：保存着某个resource在某个context中的实时指标，每个DefaultNode都指向一个ClusterNode；
             * ClusterNode：保存着某个resource在所有的context中实时指标的总和，同样的resource会共享同一个ClusterNode，不管他在哪个context中；
             */
            // Request passed, add thread count and pass count.
            node.increaseThreadNum();
            /**
             * hujianfeng
             * 上面的更新线程数，很简单，给LongAdder curThreadNum变量+1即可。
             * 但是这里的更新QPS ，就比较复杂了，涉及到"滑动时间窗口"。可在方法内部看到实现。
             * 标记[3-1]（可搜索"标记[3-2]"追踪后续代码）
             */
            node.addPassRequest(count);

            /**
             * hujianfeng
             * 标记[2-2]
             * 这里，针对当前context的调用来源（一般是app名字，或其他调用方），做专门的统计更新。
             * 一般用于针对某个调用来源（如app）来做限流。
             */
            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                context.getCurEntry().getOriginNode().increaseThreadNum();
                context.getCurEntry().getOriginNode().addPassRequest(count);
            }

            /**
             * hujianfeng
             * 这里累加全局的统计，一般是针对整个系统级别的统计（不单单是对当前资源）。
             */
            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
                Constants.ENTRY_NODE.addPassRequest(count);
            }

            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        }
        /**
         * hujianfeng
         * 下面来分析对不同流控规则的限制结果
         */
        /**
         * hujianfeng
         * 首先是 prioritized。
         * 1、只有在context在调用资源的入口使用"entry=SphU.entryWithPriority(resourceName)"的形式调用时，才会启用prioritized模式。
         * 2、这个异常的抛出，可参考com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController#canPass(Node, int, boolean)。
         *
         * 注意到，只有这个catch代码块的最后没有throw e的操作，其他catch都有throw e，所以说，
         * 对prioritized模式的流控，不会真正拒绝（只要在合理的等待时间范围内，默认是500ms，并且还要满足一定的条件，条件见：标记[5-3]）。
         * 详情可见标记[5-1]的注释。
         */
        catch (PriorityWaitException ex) {
            /**
             * hujianfeng
             * 注意一个细节：这里没有统计 addPassRequest(count)。这是因为，一旦是prioritized模式，并且走到这里的catch环节，就证明，对qps的统计，已经计入了到了未来的那个时间窗口中，而
             * 未来的时间窗口中的统计数据，会在普通时间窗口做滑动的时候，取出来并加到普通窗口的统计中，所以，这里没必要重复统计。
             * 标记[5-2]
             */
            node.increaseThreadNum();
            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                context.getCurEntry().getOriginNode().increaseThreadNum();
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
            }
            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        }
        /**
         * hujianfeng
         * 然后是 block。
         * 表示被FlowSlot所拦截。此时，将会在统计完已block的数值之后，直接抛出异常，中断本次请求。
         */
        catch (BlockException e) {
            /**
             * hujianfeng
             * 标记[7-1] 这里，在限流介入之后，会catch住限流规则抛出的异常，然后设置当前调用上线文的error为e，供后续判断（见标记[7-2])
             */
            // Blocked, set block exception to current entry.
            context.getCurEntry().setError(e);

            // Add block count.
            node.increaseBlockQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseBlockQps(count);
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseBlockQps(count);
            }

            // Handle block event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onBlocked(e, context, resourceWrapper, node, count, args);
            }

            /**
             * hujianfeng
             * 标记[6-3]
             */
            throw e;
        } catch (Throwable e) {
            // Unexpected error, set error to current entry.
            context.getCurEntry().setError(e);

            // This should not happen.
            node.increaseExceptionQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseExceptionQps(count);
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                Constants.ENTRY_NODE.increaseExceptionQps(count);
            }
            throw e;
        }
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        DefaultNode node = (DefaultNode)context.getCurNode();

        /**
         * hujianfeng
         * 标记[7-2]
         * 这里限制了：只有正常的调用退出，才会减少统计量；
         * 如果是因为BlockException限流异常导致的exit，这里的context.getCurEntry().getError()就会有值（见标记[7-1]的代码），此时，不会自动释放统计的流量。
         */
        if (context.getCurEntry().getError() == null) {
            // Calculate response time (max RT is statisticMaxRt from SentinelConfig).
            long rt = TimeUtil.currentTimeMillis() - context.getCurEntry().getCreateTime();
            int maxStatisticRt = SentinelConfig.statisticMaxRt();
            if (rt > maxStatisticRt) {
                rt = maxStatisticRt;
            }

            // Record response time and success count.
            node.addRtAndSuccess(rt, count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().addRtAndSuccess(rt, count);
            }

            node.decreaseThreadNum();

            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().decreaseThreadNum();
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                Constants.ENTRY_NODE.addRtAndSuccess(rt, count);
                Constants.ENTRY_NODE.decreaseThreadNum();
            }
        } else {
            // Error may happen.
            /**
             * hujianfeng
             * 我自己加的代码，打印e
             */
            System.out.println(context.getCurEntry().getError().getClass().getName());
        }

        // Handle exit event with registered exit callback handlers.
        Collection<ProcessorSlotExitCallback> exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks();
        for (ProcessorSlotExitCallback handler : exitCallbacks) {
            handler.onExit(context, resourceWrapper, count, args);
        }

        fireExit(context, resourceWrapper, count);
    }
}
